package cn.tradewin.advisor.security.realms;

import java.util.List;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.tapestry5.ioc.annotations.InjectService;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.slf4j.Logger;

import cn.tradewin.advisor.exceptions.DBException;
import cn.tradewin.advisor.persist.model.AccountPerm;
import cn.tradewin.advisor.persist.model.Permissions;
import cn.tradewin.advisor.persist.model.Roles;
import cn.tradewin.advisor.persist.model.Users;
import cn.tradewin.advisor.persist.services.AdvisorSecurityService;

public class AdvisorSecurityJdbcRealm extends AuthorizingRealm {

	public static final String SECURITY_NAME = "AdvisorJdbcSecurity";

	private final AdvisorSecurityService securityService;

	private final Logger log;

	public AdvisorSecurityJdbcRealm(
			@InjectService("RetryLimitHashedCredentialsMatcher") CredentialsMatcher retryLimitMatcher,
			AdvisorSecurityService securityService, Logger log) {
		super();
		this.log = log;
		this.securityService = securityService;
		this.setName(SECURITY_NAME);
		this.setCredentialsMatcher(retryLimitMatcher);
		this.setCacheManager(new MemoryConstrainedCacheManager());
	}

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		if (principals == null) {
			throw new AuthorizationException(
					"PrincipalCollection was null, which should not happen");
		}
		if (principals.isEmpty()) {
			return null;
		}
		if (principals.fromRealm(getName()).size() <= 0) {
			return null;
		}
		String username = (String) principals.fromRealm(getName()).iterator()
				.next();
		if (username == null) {
			return null;
		}
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		try {
			info.addRoles(this.getRoles(username));
			info.addStringPermissions(this.getPerms(username));
		} catch (DBException e) {
			throw new RuntimeException(e.getMessage());
		}
		return info;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken userToken) throws AuthenticationException {
		UsernamePasswordToken token = (UsernamePasswordToken) userToken;
		Users user = null;
		String username = null;
		try {
			username = token.getUsername();
			if ("root".equals(username)) {
				user = new Users();
				user.setId(username);
				user.setPassword("$shiro1$SHA-256$500000$v2qFHVd1FIYEogTkEhOMpA==$fi4yinJG6NUt9pTemEZhoxW39dzLJ2eCaA9jPvfxP9U=");
			} else {
				user = securityService.getUser(token.getUsername());
			}
		} catch (DBException e) {
			log.debug(e.getMessage());
			throw new RuntimeException(e.getMessage());
		}
		if (user == null) {
			throw new AuthenticationException("failure to login!");
		} else {
			// use password which stored in the database
			// token's password no need to using passwordService to encrypt.
			return new SimpleAuthenticationInfo(user.getId(),
					user.getPassword(), this.getName());
		}
	}

	private Set<String> getRoles(String username) throws DBException {
		List<Roles> roles = null;
		Set<String> rootRoles = null;
		if ("root".equals(username)) {
			roles = securityService.listAllRoles();
		} else {
			roles = securityService.getAssignedRolesForAccount(username);
		}
		if (roles != null) {
			rootRoles = CollectionFactory.newSet();
			for (Roles role : roles) {
				rootRoles.add(role.getRoleName());
			}
		}
		return rootRoles;
	}

	private Set<String> getPerms(String username) throws DBException {
		List<Permissions> perms = null;
		List<AccountPerm> aPerms = null;
		Set<String> rootPerms = null;
		if ("root".equals(username)) {
			perms = securityService.listAllPerms();
			if (perms != null) {
				rootPerms =  CollectionFactory.newSet();
				for (Permissions p : perms) {
					rootPerms.add(p.getPermName());
				}
			}
		} else {
			aPerms = securityService.getAuthorizedPermsForAccount(username);
			if (aPerms != null) {
				rootPerms =  CollectionFactory.newSet();
				for (AccountPerm p : aPerms) {
					rootPerms.add(p.getPermName());
				}
			}
		}
		return rootPerms;
	}
}